package com.wang.tool.idem;

import com.wang.tool.common.SimpleException;
import com.wang.tool.idem.core.IdemBody;
import com.wang.tool.idem.core.IdemConfig;
import com.wang.tool.idem.core.IdemEntity;
import com.wang.tool.idem.core.IdemEventTypeEnum;
import com.wang.tool.lock.LockServer;
import com.wang.tool.lock.core.LockBody;
import com.wang.tool.lock.core.LockException;
import com.wang.tool.monitor.InvokeTemplate;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.monitor.core.MonitorInfo;
import com.wang.tool.monitor.template.MonitorInvoker;

import java.text.MessageFormat;
import java.util.function.Supplier;

/**
 * 幂等服务
 * 幂等需要防并发,间接依赖锁服务
 *
 * @Author wangshaopeng
 * @Date 2022/6/11
 */
public abstract class IdemServer {

    /**
     * 幂等执行
     * 除了保存幂等结果异常会被吞掉,其余异常均会抛出
     *
     * @param body   幂等请求 非空
     * @param action 幂等操作 非空,内部抛出异常则向外抛出并且不会进行幂等存储
     * @return 返回值
     * @throws SimpleException          加锁失败
     * @throws IllegalArgumentException 参数异常
     */
    public IdemEntity idemDo(IdemBody body, Supplier<IdemEntity> action) throws SimpleException, IllegalArgumentException {
        if (body == null || body.getNamespace()==null || body.getIdemId() == null || action == null) {
            throw new IllegalArgumentException("idemBody/action can not be null");
        }
        String namespace = body.getNamespace();
        String idemId = body.getIdemId();

        IdemConfig idemConfig = IdemConfigCenter.getIdemConfig(namespace);
        if(!idemConfig.isOpen()){
            LinkLogUtil.info("idem is close,{}",namespace);
            return action.get();
        }

        return InvokeTemplate.monitor("IdemServer", namespace, "idemDo", new MonitorInvoker<IdemEntity>() {
            private boolean storeError = false;

            @Override
            public void weaveOnBefore(MonitorInfo<IdemEntity> monitorInfo) {
                //幂等id
                monitorInfo.addDigest("idemId", idemId);
            }

            @Override
            public IdemEntity internalCall() {
                LockBody lockBody = body.getLockBody();
                //取历史
                try {
                    IdemEntity validEntity = getValidEntity(namespace, idemId);
                    if (validEntity != null) {
                        fireEvent(IdemEventTypeEnum.IDEM_HIT, namespace, idemId, validEntity, null);
                        return validEntity;
                    }
                    if (lockBody == null) {
                        fireEvent(IdemEventTypeEnum.IDEM_MISS, namespace, idemId, null, null);
                    }
                } catch (Throwable e) {
                    fireEvent(IdemEventTypeEnum.GET_IDEM_FAIL, namespace, idemId, null, e);
                }
                //不需要锁
                if (lockBody == null) {
                    //执行业务
                    IdemEntity entity = action.get();
                    //为空当做空值永久存储
                    entity = entity != null ? entity : IdemEntity.build(null);
                    //保存
                    if (entity.judgeNeedStore()) {
                        try {
                            storeEntity(namespace, idemId, entity);
                            fireEvent(IdemEventTypeEnum.STORE_ENTITY_SUCCESS, namespace, idemId, entity, null);
                        } catch (Throwable e) {
                            storeError = true;
                            fireEvent(IdemEventTypeEnum.STORE_ENTITY_FAIL, namespace, idemId, entity, e);
                        }
                    }
                    return entity;
                }
                //加锁执行
                return lockDo(lockBody, () -> {
                    //double check

                    try {
                        IdemEntity validEntity1 = getValidEntity(namespace, idemId);
                        if (validEntity1 != null) {
                            fireEvent(IdemEventTypeEnum.IDEM_HIT, namespace, idemId, validEntity1, null);
                            return validEntity1;
                        }
                        fireEvent(IdemEventTypeEnum.IDEM_MISS, namespace, idemId, null, null);
                    } catch (Throwable e) {
                        fireEvent(IdemEventTypeEnum.GET_IDEM_FAIL, namespace, idemId, null, e);
                    }
                    //执行业务
                    IdemEntity entity = action.get();
                    //为空当做空值永久存储
                    entity = entity != null ? entity : IdemEntity.build(null);
                    //保存
                    if (entity.judgeNeedStore()) {
                        try {
                            storeEntity(namespace, idemId, entity);
                            fireEvent(IdemEventTypeEnum.STORE_ENTITY_SUCCESS, namespace, idemId, entity, null);
                        } catch (Throwable e) {
                            storeError = true;
                            fireEvent(IdemEventTypeEnum.STORE_ENTITY_FAIL, namespace, idemId, entity, e);
                        }
                    }
                    return entity;
                });
            }

            @Override
            public void weaveOnAfter(MonitorInfo<IdemEntity> monitorInfo) {
                IdemEntity result = monitorInfo.getResult();
                //是否保存
                monitorInfo.addDigest("needStore", result != null && result.judgeNeedStore());
                //是否保存成功
                monitorInfo.addDigest("storeError", storeError);
            }
        });
    }


    /**
     * 获取有效幂等结果
     *
     * @param namespace  非空
     * @param idemId 非空
     * @return
     */
    protected abstract IdemEntity getValidEntity(String namespace, String idemId);


    /**
     * 保存幂等结果
     *
     * @param namespace  非空
     * @param idemId 非空
     * @param entity 非空
     */
    protected abstract void storeEntity(String namespace, String idemId, IdemEntity entity);

    /**
     * 加锁执行
     * 建议依赖锁服务
     *
     * @param body   非空
     * @param action 非空
     * @return
     * @see LockServer#lockDo
     */
    protected abstract IdemEntity lockDo(LockBody body, Supplier<IdemEntity> action) throws LockException;


    protected void fireEvent(IdemEventTypeEnum eventType, String namespace, String idemId, IdemEntity idemEntity, Throwable e) {
        if (eventType == IdemEventTypeEnum.GET_IDEM_FAIL || eventType == IdemEventTypeEnum.STORE_ENTITY_FAIL) {
            throw new SimpleException(e, eventType.name(), MessageFormat.format("namespace:{0}, idemId:{1}, idemEntity:{2}", namespace, idemId, idemEntity));
        }
    }


}
